package citools

import (
	"errors"
	"fmt"
	"github.com/bitly/go-simplejson"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
)

var (
	DefaultIgnoreReg *regexp.Regexp = regexp.MustCompile(`/\.`)
)

func FormatPath(p string) string {
	p = strings.Replace(p, "\\", "/", -1)
	return p
}

func GetDirFiles(dirname string, ignoreDir []string, ignoreReg *regexp.Regexp) (files []string, err error) {
	var (
		dir  *os.File
		f    *os.File
		stat os.FileInfo
		list []os.FileInfo
	)
	//打开文件资源
	if dir, err = os.Open(dirname); err != nil {
		return
	}

	//如果dirname是一个文件，则直接返回该文件
	if stat, err = dir.Stat(); err != nil {
		return
	}

	if stat.IsDir() == false {
		err = errors.New("dirname必须是一个目录")
		return
	}

	//如果dirname是一个目录，则遍历目录
	stack := []string{dirname}
	dir.Close()
	for {
		if len(stack) == 0 {
			break
		}

		a := stack
		var x string
		x, a = a[0], a[1:]
		stack = a

		if f, err = os.Open(x); err != nil {
			return
		}

		if list, err = f.Readdir(0); err != nil {
			f.Close()
			return
		} else {
			for _, fi := range list {
				newName := x + "/" + fi.Name()
				if ignoreReg != nil {
					if ignoreReg.MatchString(fi.Name()) {
						continue
					}
					if ignoreReg.MatchString(newName) {
						continue
					}
				}

				if fi.IsDir() {
					if ignoreDir != nil {
						flag := false
						for _, val := range ignoreDir {
							ignore := strings.Index(newName, val)
							if ignore != -1 {
								flag = true
								break
							}
						}

						if flag == true {
							continue
						}
					}

					stack = append(stack, newName)
				} else {

					newName = FormatPath(newName)
					files = append(files, newName)
				}
			}
		}

		f.Close()
	}

	return
}

// / Json-修改json文件file的值
// / 会格式化文件
func JsonModify(file string, key string, val interface{}) {
	//打开文件资源
	b, err := ioutil.ReadFile(file)
	if err != nil {
		panic(fmt.Sprintf("JsonModify error: %v\n", err))
	}

	json, err := simplejson.NewJson(b)
	if err != nil {
		panic(fmt.Sprintf("JsonModify error: %v\n", err))
	}

	keyarr := strings.Split(key, ".")
	cur := json
	for {
		first := keyarr[0]
		keyarr = keyarr[1:]

		if len(keyarr) == 0 {
			cur.Set(first, val)
			break
		}

		key, err := strconv.Atoi(first)
		if err != nil {
			cur = cur.Get(first)
		} else {
			cur = cur.GetIndex(key)
		}
	}

	r, err := json.EncodePretty()
	if err != nil {
		panic(fmt.Sprintf("JsonModify error: %v\n", err))
	}

	stat, err := os.Stat(file)
	if err != nil {
		panic(fmt.Sprintf("JsonModify error: %v\n", err))
	}

	ioutil.WriteFile(file, r, stat.Mode())
}

// / Json-合并src目录中所有的json文件到目标tar文件中
func JsonCombineDir(src string, tar string) {
	files, err := GetDirFiles(src, nil, DefaultIgnoreReg)
	if err != nil {
		panic(fmt.Sprintf("JsonCombineDir error: %v\n", err))
	}

	combine := simplejson.New()
	for _, file := range files {
		b, err := ioutil.ReadFile(file)
		if err != nil {
			panic(fmt.Sprintf("JsonModify error: %v\n", err))
		}

		json, err := simplejson.NewJson(b)
		if err != nil {
			panic(fmt.Sprintf("JsonModify error: %v\n", err))
		}

		m, err := json.Map()
		if err != nil {
			panic(fmt.Sprintf("JsonModify error: %v\n", err))
		}

		for mkey, mval := range m {
			combine.Set(mkey, mval)
		}
	}

	r, err := combine.MarshalJSON()
	if err != nil {
		panic(fmt.Sprintf("JsonCombineDir error: %v\n", err))
	}
	ioutil.WriteFile(tar, r, 755)
}

func Sync(source, target string, info bool) (err error) {
	return SyncIgnore(source, target, info, nil)
}

// 拷贝文件  要拷贝的文件路径 拷贝到哪里
func SyncIgnore(source, target string, info bool, ignoreDir []string) (err error) {
	var (
		sourcelist []string
		targetlist []string
		sourceDir  *os.File
		targetDir  *os.File
		stat       os.FileInfo
		srcpath    string

		isSourceFile bool
		//isTargetFile bool
	)
	if source == "" || target == "" {
		err = errors.New("参数不正确")
		return
	}

	if sourceDir, err = os.Open(source); err != nil {
		return
	}
	defer sourceDir.Close()

	if stat, err = sourceDir.Stat(); err != nil {
		return
	}

	if stat.IsDir() == true {
		srcpath = source
		if sourcelist, err = GetDirFiles(source, nil, nil); err != nil {
			return err
		}
	} else {
		srcpath = path.Dir(source)
		sourcelist = append(sourcelist, source)
		isSourceFile = true
	}

	//打开文件资源
	//只写模式打开文件 如果文件不存在进行创建 并赋予 755的权限。详情查看linux 权限解释
	targetDir, targeterr := os.Open(target)
	if targeterr != nil {
		//如果目标是个目录，或者源是一个目录，则目标表示是一个目录
		if target[len(target)-1] == "/"[0] {
			if err = os.MkdirAll(target, 0755); err != nil {
				return
			}
		} else {
			if isSourceFile == false {
				if err = os.MkdirAll(target, 0755); err != nil {
					return
				}
			} else {
				SyncFile(source, target)
				return
			}
		}

		targetDir, err = os.Open(target)
		if err != nil {
			return
		}
	}

	//养成好习惯。操作文件时候记得添加 defer 关闭文件资源代码
	defer targetDir.Close()
	if stat, err = targetDir.Stat(); err != nil {
		return
	}

	//如果目标不是一个目录，则源必须是一个文件
	if stat.IsDir() != true {
		if isSourceFile == true {
			SyncFile(source, target)
			return
		}
		return
	}

	if targetlist, err = GetDirFiles(target, nil, nil); err != nil {
		return err
	}

	tlist := []string{}
	for _, fname := range sourcelist {
		tname := strings.Replace(fname, srcpath+"/", "", -1)
		tlist = append(tlist, tname)
	}
	sourcelist = tlist

	tlist = []string{}
	for _, fname := range targetlist {
		tname := strings.Replace(fname, target+"/", "", -1)
		tlist = append(tlist, tname)
	}
	targetlist = tlist

	//进行数据拷贝
	if info {
		fmt.Printf("开始同步（目录/文件）：%v\n", source)
		fmt.Printf("同步到目录： %v\n", target)
	}
	for _, fname := range sourcelist {
		SyncFile(srcpath+"/"+fname, target+"/"+fname)
	}
	return
}

func SyncDir(source string, target string, ignore *regexp.Regexp) {

	fmt.Printf("SyncDir %v->%v\n", source, target)
	var (
		err error
	)

	if source == "" || target == "" {
		panic("参数不正确")
	}

	sourceDir, err := os.Open(source)
	if err != nil {
		panic(err)
	}
	defer sourceDir.Close()

	srcstat, err := sourceDir.Stat()
	if err != nil {
		panic("参数不正确")
	}

	if srcstat.IsDir() == false {
		panic("源路径不是目录")
	}

	srcfiles, err := GetDirFiles(source, nil, ignore)
	if err != nil {
		panic(err)
	}

	for _, filename := range srcfiles {
		fname := strings.Replace(filename, source+"/", "", -1)
		SyncFile(filename, target+"/"+fname)
	}
}

func SyncFile(source string, target string) {
	sstat, err := os.Stat(source)
	if err != nil {
		panic(err)
	}

	tstat, err := os.Stat(target)
	// 如果文件不存在，直接同步
	// 如果文件存在，检查修改时间
	if err == nil {
		if sstat.ModTime().Unix() == tstat.ModTime().Unix() && sstat.Size() == tstat.Size() {
			return
		}

	}

	CopyFile(source, target)
	err = os.Chtimes(target, sstat.ModTime(), sstat.ModTime())
	if err != nil {
		panic(err)
	}
	//os.Chown(target, os.Getuid(), os.Getgid())
}

func CopyFile(source, dest string) {

	if source == "" || dest == "" {
		panic(fmt.Sprintf("Copy的参数不正确[%v,%v]", source, dest))
	}
	fmt.Printf("Copy %v -> %v\n", source, dest)

	// 路径不存在，创建路径
	tardir := filepath.Dir(dest)
	_, err := os.Stat(tardir)
	if err != nil {
		if err = os.MkdirAll(tardir, 0755); err != nil {
			return
		}
	}
	//打开文件资源
	source_open, err := os.Open(source)
	//养成好习惯。操作文件时候记得添加 defer 关闭文件资源代码
	if err != nil {
		panic(err)
	}
	defer source_open.Close()
	//只写模式打开文件 如果文件不存在进行创建 并赋予 644的权限。详情查看linux 权限解释
	dest_open, err := os.OpenFile(dest, os.O_CREATE|os.O_WRONLY, 0755)
	if err != nil {
		panic(err)
	}

	//养成好习惯。操作文件时候记得添加 defer 关闭文件资源代码
	defer dest_open.Close()
	//进行数据拷贝

	err = dest_open.Truncate(0)
	if err != nil {
		panic(err)
	}

	_, copy_err := io.Copy(dest_open, source_open)
	if copy_err != nil {
		panic(err)
	}
}

func CommandByArgs(cmd string, args map[string]string, params ...string) (err error) {
	argstr := ""
	for k, v := range args {
		if len(k) == 1 {
			argstr += fmt.Sprintf(" -%v %v", k, v)
		} else {
			argstr += fmt.Sprintf(" --%v=%v", k, v)
		}
	}

	paramstr := ""
	for _, v := range params {
		paramstr += fmt.Sprintf(" %v", v)
	}

	cmdstr := fmt.Sprintf(`%v%v%v`, cmd, argstr, paramstr)
	fmt.Println(cmdstr)
	var gocmd *exec.Cmd
	if runtime.GOOS == "windows" {
		gocmd = exec.Command("CMD", "/C", cmdstr)
	} else {
		gocmd = exec.Command("bash", "-c", cmdstr)
	}

	gocmd.Stdout = os.Stdout
	gocmd.Stderr = os.Stderr
	err = gocmd.Run()
	return err
}

func CommandGet(cmd string) (result string, err error) {
	cmdstr := fmt.Sprintf(`%v`, cmd)
	var gocmd *exec.Cmd
	if runtime.GOOS == "windows" {
		gocmd = exec.Command("CMD", "/C", cmdstr)
	} else {
		gocmd = exec.Command("bash", "-c", cmdstr)
	}

	gocmd.Stdout = os.Stdout
	gocmd.Stderr = os.Stderr
	err = gocmd.Run()
	return "", err
}

func CommandExecNoPanic(cmd string) {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
		}
	}()
	cmdstr := fmt.Sprintf(`%v`, cmd)
	var gocmd *exec.Cmd
	if runtime.GOOS == "windows" {
		gocmd = exec.Command("CMD", "/C", cmdstr)
	} else {
		gocmd = exec.Command("bash", "-c", cmdstr)
	}

	fmt.Println(cmd)
	gocmd.Stdout = os.Stdout
	gocmd.Stderr = os.Stderr
	err := gocmd.Run()

	if err != nil {
		panic(err)
	}
}

func CommandExec(cmd string) {
	cmdstr := fmt.Sprintf(`%v`, cmd)
	var gocmd *exec.Cmd
	if runtime.GOOS == "windows" {
		gocmd = exec.Command("CMD", "/C", cmdstr)
	} else {
		gocmd = exec.Command("sh", "-c", cmdstr)
	}

	fmt.Println(cmd)
	gocmd.Stdout = os.Stdout
	gocmd.Stderr = os.Stderr
	err := gocmd.Run()

	if err != nil {
		panic(err)
	}
}

// / 删除文件
func RemoveFile(target string) {
	fmt.Printf("RemoveFile %v\n", target)
	err := os.Remove(target)
	if err != nil {
		fmt.Println("RemoveFile Error: ", err.Error())
	}
}

// / 删除目录
func RemoveDir(dir string) {
	fmt.Printf("RemoveDir %v\n", dir)
	err := os.RemoveAll(dir)
	if err != nil {
		panic(err)
	}
}

// / 替换文本
func ReplaceText(file string, old string, new string) {
	fmt.Printf("ReplaceText %v %v %v\n", file, old, new)
	var b, err = ioutil.ReadFile(file)
	if err != nil {
		panic(err)
	}

	var re, _ = regexp.Compile(old)
	var content = re.ReplaceAllString(string(b), new)

	ioutil.WriteFile(file, []byte(content), os.ModePerm)
}

// / 替换文本，通过func
func ReplaceTextFunc(file string, old string, repl func(string) string) {
	fmt.Printf("ReplaceTextFunc %v %v\n", file, old)
	var b, err = ioutil.ReadFile(file)
	if err != nil {
		panic(err)
	}

	var re, _ = regexp.Compile(old)
	var content = re.ReplaceAllStringFunc(string(b), repl)

	ioutil.WriteFile(file, []byte(content), os.ModePerm)
}
